const std = @import("std");

// 尽管这个函数看起来是命令式的, 但它并不会直接执行构建操作,
// 而是通过修改构建图(b)来定义构建步骤. 这个构建图之后会由外部运行器执行.
// std.Build中的函数实现了一种DSL(领域特定语言), 用于定义构建步骤及其依赖关系,
// 允许构建运行器自动并行化构建任务(缓存系统也能据此判断何时不需要重新运行步骤).
pub fn build(b: *std.Build) void {
    // 标准目标平台选项允许运行 `zig build` 的用户选择目标平台.
    // 这里我们没有覆盖默认值, 意味着允许任何目标平台, 默认是本地平台.
    // 还有其他选项可以限制支持的目标平台集合.
    const target = b.standardTargetOptions(.{});

    // 标准优化选项允许运行 `zig build` 的用户选择
    // Debug, ReleaseSafe, ReleaseFast 和 ReleaseSmall 优化模式. 这里我们没有
    // 设置默认的发布模式, 让用户决定如何优化.
    const optimize = b.standardOptimizeOption(.{});

    // 还可以通过 `b.option()` 定义更多自定义标志位来切换构建脚本的可选功能.
    // 所有定义的标志位(包括目标平台和优化选项)都会在运行
    // `zig build --help` 时显示.

    // 创建一个模块, 它表示一组源文件及其编译选项(如优化模式和链接的系统库).
    // Zig模块是向使用者提供代码的首选方式.
    // addModule 定义了一个我们打算暴露给使用者的模块.
    // 必须给它命名, 因为一个Zig包可以暴露多个模块, 使用者需要指定访问哪个模块.
    const mod = b.addModule("zig_hello", .{
        // 根源文件是这个模块的"入口点". 使用者只能访问这个文件中声明的公共符号,
        // 如果模块的其他文件中有需要暴露的符号, 必须在这个根文件中重新导出.
        .root_source_file = b.path("src/root.zig"),
        // 后面我们将用这个模块作为测试可执行文件的根模块, 因此需要指定目标平台.
        .target = target,
    });

    // 定义一个可执行文件. 可执行文件需要有包含 `main` 函数的根模块.
    // 虽然我们可以将上面的模块添加main函数, 但有时将业务逻辑和CLI拆分为两个独立模块更合理.
    //
    // 如果你的目标是创建Zig库供他人使用, 可以考虑是否需要同时提供CLI工具.
    // 例如一个数据序列化格式解析库可以附带CLI语法检查器.
    //
    // 如果你的目标是创建可执行文件, 可以考虑是否需要允许用户将其核心功能
    // 嵌入到他们自己的程序中以避免调用CLI工具的开销.
    //
    // 如果都不适用, 可以删除不需要的声明, 将所有内容放在一个模块中.
    const exe = b.addExecutable(.{
        .name = "zig_hello",
        .root_module = b.createModule(.{
            // b.createModule 创建的新模块不会暴露给包的使用者,
            // 因此不需要命名.
            .root_source_file = b.path("src/main.zig"),
            // 可执行文件/库的根模块需要显式指定目标平台和优化级别,
            // 也可以硬编码特定目标(例如嵌入式设备的固件).
            .target = target,
            .optimize = optimize,
            // 根模块可用导入的模块列表.
            .imports = &.{
                // "zig_hello" 是你在源代码中导入该模块时使用的名称(例如 `@import("zig_hello")`).
                // 重复写名称是因为允许重命名导入(应对不同包模块名称冲突的情况).
                .{ .name = "zig_hello", .module = mod },
            },
        }),
    });

    // 声明将可执行文件安装到安装前缀目录(运行 `zig build` 时默认步骤).
    // 默认安装前缀是 `zig-out/`, 但可以通过 `--prefix` 或 `-p` 参数覆盖.
    b.installArtifact(exe);

    // 创建顶级步骤. 顶级步骤可以通过名称调用(例如 `zig build run`).
    // 顶级步骤要实际执行, 必须依赖其他步骤(例如运行步骤).
    const run_step = b.step("run", "运行应用程序");

    // 创建一个RunArtifact步骤. 该步骤会运行Zig编译的可执行文件.
    // 步骤只有在被用户直接调用(顶级步骤)或被其他步骤依赖时才会执行.
    // 这里我们希望在运行 `zig build run` 时执行, 所以建立依赖关系.
    const run_cmd = b.addRunArtifact(exe);
    run_step.dependOn(&run_cmd.step);

    // 让运行步骤依赖安装步骤, 这样会从安装目录运行程序而不是直接从缓存目录运行.
    run_cmd.step.dependOn(b.getInstallStep());

    // 允许用户通过构建命令传递参数, 例如: `zig build run -- arg1 arg2`
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    // 创建运行模块测试的可执行文件. 这里 `mod` 需要指定目标平台,
    // 这就是为什么前面要设置该字段.
    const mod_tests = b.addTest(.{
        .root_module = mod,
    });

    // 运行模块测试的步骤.
    const run_mod_tests = b.addRunArtifact(mod_tests);

    // 创建运行可执行文件根模块测试的可执行文件.
    // 注意测试可执行文件每次只能测试一个模块, 因此需要创建两个不同的测试.
    const exe_tests = b.addTest(.{
        .root_module = exe.root_module,
    });

    // 运行可执行文件测试的步骤.
    const run_exe_tests = b.addRunArtifact(exe_tests);

    // 创建运行所有测试的顶级步骤. 由于两个测试步骤互不依赖, 会并行执行.
    const test_step = b.step("test", "运行所有测试");
    test_step.dependOn(&run_mod_tests.step);
    test_step.dependOn(&run_exe_tests.step);

    // 顶级步骤和标志位一样, 都会显示在 `--help` 菜单中.
    //
    // Zig构建系统完全在用户态实现, 不能访问编译器私有API.
    // 所有构建工作最终都会转换为对应的Zig编译器子命令.
    // 当构建失败或启用详细模式时, 可以观察这些调用以验证假设和诊断问题.
    //
    // 最后, Zig构建系统相对简单且自包含, 阅读其源码可以深入掌握.
}
